#include "interpret.h"
#include "int_expr.h"
#include "int_exec.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#ifdef WIN32
#include "def_for_win.h"
#endif //WIN32

#include "logo_lib_p.h"

#define MAIN_STR "+++Main+++"

static int pickup_routines(tree_node_t* t, routine_t* main_rtn, routine_t* pos);
static void list_rte (routine_t* lst_head);
static int push_stack (rte_t* rte, routine_t* rtn, value_t* paras);
static void pop_stack(rte_t* rte);
static void add_predefined_routines (routine_t** pos);

static int logo_random (value_t* paras, int para_numbers, value_t* result);
static int logo_inputwindow (value_t* paras, int para_numbers, value_t* result);
static int logo_sqrt(value_t* paras, int para_numbers, value_t* result);
static int logo_int(value_t* paras, int para_numbers, value_t* result);
static int logo_mod(value_t* paras, int para_numbers, value_t* result);
static int logo_sin(value_t* paras, int para_numbers, value_t* result);
static int logo_cos(value_t* paras, int para_numbers, value_t* result);

static int is_numeric (char* s);

value_t::value_t()
{
   type = NoneVal;
   str = NULL;
}

value_t::~value_t()
{
   if (type == StringVal && str) {
      free (str);
   }
}

void value_t::copy_value(value_t* src)
{
   assert (src);

   if (type == StringVal && str) {
      free (str);
      str = NULL;
   }

   type = src->type;
   if (type != StringVal) {
      val = src->val;
      return;
   }

   assert (type == StringVal);
   str = (char*) malloc (strlen (src->str) +1);
   CHECK_MEM (str);
   strcpy (str, src->str);
   return;
}

void value_t::copy_string(const char* s)
{
   assert (s);
   assert (type == StringVal);
   
   if (str) {
      free(str);
      str = NULL;
   }

   str = (char*) malloc (strlen (s) +1);
   CHECK_MEM (str);
   strcpy (str, s);
   return;
}

stack_t::stack_t ()
{
   top = -1;
   memset (var, 0, sizeof(var));
}

stack_t::~stack_t()
{
   for (int i = 0; i <= top; i++) {
      if (var[i]) {
         delete_variable_list (var[i]);
      }
   }
}

rte_t::rte_t()
{
   rtn_lst = NULL;
   mutex = NULL;
   exec = NULL;
   break_continue_line_no = -1;
}

rte_t::~rte_t()
{
   if (rtn_lst) {
      delete_routine_list(rtn_lst);
   }
}

int debug_get_call_stack(exe_handle_t hexe, int* nIdx, char* buf, int buflen, int* lineno)
{
   assert (hexe);
   assert (nIdx);

   rte_t* rte = (rte_t*)hexe;
   if (rte->stack.top == -1) {
      return -1;
   }
   if (*nIdx > rte->stack.top ) {
      return -1;
   }
   if (*nIdx < 0) {
      *nIdx = rte->stack.top;
   }

   if (buf) {
      assert (buflen > 0);
      buf[buflen -1] = '\0';
      strncpy(buf, rte->stack.rtn[*nIdx]->node_head.id_str, buflen);
   }
   if (lineno) {
      *lineno = rte->stack.lineno[*nIdx];
   }
   if (*nIdx == 0) {
      *nIdx = rte->stack.top + 1; // so, next invocation will return -1
   } else {
      (*nIdx)--;
   }
   
   return 0;
}

var_handle_t debug_get_first_variable(exe_handle_t hexe, int nStackIdx, const char** name, value_t** val)
{
   assert (hexe);
   assert (nStackIdx >= 0);
   assert (name);
   assert (val);

   rte_t* rte = (rte_t*) hexe;
   if (nStackIdx > rte->stack.top) {
      return NULL;
   }
   
   variable_t* head = rte->stack.var[nStackIdx];
   if (!head) {
      return NULL;
   }
   *name = head->node_id;
   *val = &(head->value);
   return (var_handle_t)head;
}

var_handle_t debug_get_next_variable(var_handle_t hvar, const char** name, value_t** val)
{
   assert (hvar);
   assert (name);
   assert (val);

   variable_t* next = (variable_t*)(((variable_t*)hvar)->next_node);
   if (!next) {
      return NULL;
   }
   *name = next->node_id;
   *val = &(next->value);
   return (var_handle_t)next;
}

var_handle_t debug_find_varriable_by_string (exe_handle_t hexe, int nStackIdx, const char* name)
{
   assert (hexe);
   assert (nStackIdx >= 0);

   rte_t* rte = (rte_t*) hexe;
   if (nStackIdx > rte->stack.top) {
      return NULL;
   }
   
   variable_t* cur = rte->stack.var[nStackIdx];
   while (cur) {
      if (strcmp(name, cur->node_id) == 0) {
         return (var_handle_t)cur;
      }
      cur = (variable_t*)(cur->next_node);
   }
   return NULL;
}

int debug_change_variable_numeric_value(var_handle_t hvar, double v)
{
   assert (hvar);

   variable_t* var = (variable_t*)hvar;
   assert( var->value.type == NumericVal);
   var->value.val = v;
   return 0;
}

int debug_change_variable_string_value(var_handle_t hvar, const char* s)
{
   assert (hvar);
   assert (s);

   variable_t* var = (variable_t*)hvar;
   var->value.copy_string(s);
   return 0;
}

int debug_get_routine_lineno_range(exe_handle_t hexe, int nStackIdx, int* start, int* end)
{
   assert (start && end);
   assert (hexe);
   assert (nStackIdx >= 0);

   rte_t* rte = (rte_t*) hexe;
   if (nStackIdx > rte->stack.top) {
      return -1;
   }
   if (rte_rtn(rte, nStackIdx)->func ){
      return -1;
   }

   *start = rte_rtn(rte, nStackIdx)->tree->line_no;
   const tree_node_t* rightmost = get_rightmost_node (rte_rtn(rte, nStackIdx)->tree);
   *end = rightmost->line_no;
   return 0; 
}

list_node_t* find_in_list (list_node_t* list, const char* id_str)
{
   list_node_t* p;

   assert (id_str);

   p = list;
   while (p) {
      if (strcmp (p->id_str, id_str) == 0) {
         return p;
      }
      p = p->next;
   }
   return NULL;
}

void delete_variable (variable_t* v)
{
   assert (v);
   if (v->node_id) {
      free (v->node_id);
   }
   if (v->value.type == StringVal) {
      free (v->value.str);
   } 
   free (v);
}

void delete_variable_list (variable_t* list)
{
   variable_t* p = list;

   assert (list);

   while (p) {
      variable_t* next = (variable_t*) (p->next_node);
      delete_variable (p);
      p = next;
   }
}

routine_t* new_routine ()
{
   routine_t* r = (routine_t*) malloc (sizeof (routine_t));
   CHECK_MEM (r);

   memset (r, 0, sizeof(routine_t));
   return r;
}

void delete_routine (routine_t* r)
{
   assert (r);
   if (r->tree) {
      delete_tree (r->tree);
   }

   free (r);
}

void delete_routine_list (routine_t* list)
{
   routine_t* p = list;

   assert (list);

   while (p) {
      routine_t* next = (routine_t*) (p->next_node);
      delete_routine (p);
      p = next;
   }
}

void set_exec_obj (exe_handle_t hexe, i_exec_t* exec)
{
   assert (exec);
   assert (hexe);

   ((rte_t*)hexe)->exec = exec;
   g_exec = exec;
}

void interpret (exe_handle_t h, node_handle_t t)
{
   assert (h);

   routine_t *pos;
   value_t val;
   rte_t*  rte = (rte_t*)h;

   assert (!g_syntax_error);
   assert (rte->exec);

   if (!t) {
      (rte->exec)->done();
      return;
   }

   /* If in debug mode, wait for the first debug command.
    * Ignored if not debug mode.
    */
   rte->control.DbgCtrl.waitfornext = true;
   /* set the random seed. */
   srand ((unsigned int)(time(NULL)));
   rte->rtn_lst = new_routine ();
   /* The list head is the 'main'. */
   rte->rtn_lst->tree = NULL;
   rte->rtn_lst->node_id = MAIN_STR;

   pos = rte->rtn_lst;
   add_predefined_routines (&pos);
   if (pickup_routines ((tree_node_t*)t, rte->rtn_lst, pos) < 0) {
      (rte->exec)->done();
      return;
   }

   list_rte (rte->rtn_lst);

   /* Execute the "main routine." */
   int ret = exec_routine (rte, rte->rtn_lst, NULL, 0, &val);

   if (ret == RESULT_ABORTTED_BY_USER) {
      print_info (ERROR_INFO, "Aborted by user.");
   }
   if (get_debug_mode(rte)) {
      /* the last debug ack cannot be sent in exec_stmt. */
      (rte->exec)->debug_cmd_ack(0);
   }
   (rte->exec)->done();
   return;
}


/* ---- Helpers ---- */
static int pickup_routines(tree_node_t* t, routine_t* main_rtn, routine_t* pos)
{
   tree_node_t *cur = t, *pre = NULL, *next = NULL;

   routine_t* tail = pos;
   assert (t);
   assert (pos);
   assert (main_rtn);

   pre = NULL;
   cur = t;
   while (cur) {
      next = cur->sibling;
      if (cur->node_type == StmtNode && cur->type.stmt == TOKEN_LEARN) {
         if (find_in_list ((list_node_t*)main_rtn, cur->attr.id_str)) {
            /* a routine with same name exists. */
            print_info (ERROR_INFO, "A routine named '%s' already exists. At line %d.",
                     cur->attr.id_str, cur->line_no);
            return -1;
         }
         routine_t* rtn = new_routine();
         rtn->tree = cur;
         if (pre) {
            pre->sibling = cur->sibling;
         }
         cur->sibling = NULL;
         rtn->node_id = cur->attr.id_str; 
         tail->next_node = (list_node_t*)rtn;
         tail = rtn;
      } else  {
         if (main_rtn->tree == NULL) {
            main_rtn->tree = cur;
         }
         pre = cur;
      }
      cur = next;
   }
   return 0;
}

static void list_rte (routine_t* lst_head)
{
   routine_t* cur;
   assert (lst_head);
   cur = lst_head;
   
   while (cur) {
      print_info (DEBUG_INFO, "------ Routine %s: --------\r\n", cur->node_id);
      if (cur->func) {
         assert (!cur->tree);
         print_info (DEBUG_INFO, "Predefined routine %p\r\n", cur->func);
      } else if (cur->tree) {
         print_info (DEBUG_INFO, "R  ");
         print_parse_tree (cur->tree);
      } else {
         print_info (DEBUG_INFO, "NO CONTENT\r\n");
      }
      cur = (routine_t*) cur->next_node;
   }
}

int exec_routine (rte_t* rte, routine_t* rtn, value_t* paras, int para_number, value_t* result)
{
   int ret;
   assert (rtn);
   assert (rte->rtn_lst);
   assert (result);
   assert (rte);

   if (rtn->func) {
      /* this is a predefined routine.*/
      ret = rtn->func(paras, para_number, result);
      if (ret != 0) {
          return ret;
      }
      return 0;
   }

   assert (rtn->tree->node_type == StmtNode);
   if (!rtn->tree) {
      result->type = NoneVal;
      return 0;
   }

   ret = push_stack (rte, rtn, paras); // push statck
   if (ret < 0) {
      return ret;
   }
   if (strcmp (rtn->node_id, MAIN_STR) == 0) {
      ret = exec_stmt_seq (rte, rtn->tree, result);
      pop_stack(rte);
      if ((ret == RESULT_BREAK || ret == RESULT_CONTINUE) && rte->break_continue_line_no >= 0) {
         print_info (ERROR_INFO, "break or continue statement outside of loop at line %d.", rte->break_continue_line_no);
         rte->break_continue_line_no = -1;
      } 
      return ret;
   }

   assert (rtn->tree->type.stmt == TOKEN_LEARN);
   print_var_list(rte);
   ret = exec_stmt_seq (rte, rtn->tree->child[1], result);
   pop_stack (rte);
   if ((ret == RESULT_BREAK || ret == RESULT_CONTINUE) && rte->break_continue_line_no >= 0) {
      print_info (ERROR_INFO, "break or continue statement outside of loop at line %d.", rte->break_continue_line_no);
      rte->break_continue_line_no = -1;
   } else if (ret < 0 && ret != RESULT_ABORTTED_BY_USER) {
      print_info (ERROR_INFO, "Error at line %d.", cur_lineno(rte));
   }
   return ret;
}

static int push_stack (rte_t* rte, routine_t* rtn, value_t* paras)
{
   tree_node_t* location_paras;
   variable_t* var;
   int i;
   assert (rtn);
   assert (rte);

   if (rte->stack.top == LOGO_STACK_DEPTH -1) {
      print_info (ERROR_INFO, "Stack overflown when calling routine %s. Max stack depth is %d.", 
                  rtn->node_id, LOGO_STACK_DEPTH);
      return -1;
   }

   (rte->stack.top) ++;
   assert (cur_var_list(rte) == NULL);
   cur_rtn(rte) = rtn;
   cur_lineno(rte) = rtn->tree->line_no;

   if (strcmp (rtn->node_id, MAIN_STR) == 0) {
      return 0;
   }

   assert (rtn->tree->node_type == StmtNode);
   assert (rtn->tree->type.stmt == TOKEN_LEARN);

   location_paras = rtn->tree->child[0];

   i = 0;
   while (location_paras) {
      var = create_variable (rte, location_paras);
      assert (var);
      var->value = paras[i];
      location_paras = location_paras->sibling;
      i++;
   }

   return 0;
}

static void pop_stack (rte_t* rte)
{
   assert (rte);
   assert (rte->stack.top >= 0);

   if (cur_var_list(rte)) {
      delete_variable_list (cur_var_list(rte));
      cur_var_list(rte) = NULL;
   }
   (rte->stack.top) --;
   return;
}

static char* predefined_routine_names[] = {"random", "inputwindow", "sqrt", "int", "mod", "sin", "cos", NULL};
static predefined_routine_f entries[] = {logo_random, logo_inputwindow, logo_sqrt, logo_int, logo_mod, logo_sin, logo_cos, NULL};

static void add_predefined_routines (routine_t** pos)
{
   int i = 0;
   assert (pos && *pos);

   while (entries[i]) {
      routine_t* rtn;
      assert (predefined_routine_names[i]);

      rtn = new_routine();
      rtn->node_id = predefined_routine_names[i];
      rtn->func = entries[i];
      rtn->next_node = (*pos)->next_node;
      (*pos)->next_node = (list_node_t*) rtn;
      *pos = rtn;
      i++;
   }
   return;
}

static int logo_random (value_t* paras, int para_number, value_t* result)
{
   double l, h;

   assert (result);

   if (para_number != 2 || paras[0].type != NumericVal ||
       paras[1].type != NumericVal) {
      print_info (ERROR_INFO, "Routine 'random' needs 2 numeric parameters.");
      return -1;
   }
   l = paras[0].val < paras[1].val ? paras[0].val : paras[1].val;
   h = paras[0].val >= paras[1].val ? paras[0].val : paras[1].val;

   result->type = NumericVal;
   if (h-l <= 0.000001) {
      result->val = l;
      return 0;
   }

   result->val = l + ((double)rand())/((double)RAND_MAX) * (h-l);
   return 0;
}

static int logo_sqrt (value_t* paras, int para_number, value_t* result)
{
   assert (result);

   if (para_number != 1 || paras[0].type != NumericVal || paras[0].val < 0) {
      print_info (ERROR_INFO, "Routine 'sqrt' needs 1 non-negative numeric parameters.");
      return -1;
   }

   result->type = NumericVal;
   result->val = sqrt(paras[0].val);
   return 0;
}

static int logo_int (value_t* paras, int para_number, value_t* result)
{
   assert (result);

   if (para_number != 1 || paras[0].type != NumericVal ) {
      print_info (ERROR_INFO, "Routine 'int' needs 1  numeric parameters.");
      return -1;
   }

   result->type = NumericVal;
   result->val = (double)((int)(paras[0].val));
   return 0;
}

static int logo_mod(value_t* paras, int para_numbers, value_t* result)
{
   assert (result);

   if (para_numbers != 2 || paras[0].type != NumericVal  || paras[1].type != NumericVal) {
      print_info (ERROR_INFO, "Routine 'mod' needs 2  numeric parameters.");
      return -1;
   }

   if ( ((int)(paras[1].val)) == 0)  {
      print_info (ERROR_INFO, "Routine 'mod' needs parameter 2's integer part cannot be 0.");
      return -1;
   }

   result->type = NumericVal;
   result->val = (double)(((int)(paras[0].val)) - ((int)(paras[0].val))/((int)(paras[1].val)) * ((int)(paras[1].val)));
   return 0;
}

#define PI                       3.1415927

static int logo_sin(value_t* paras, int para_numbers, value_t* result)
{
   assert (result);

   if (para_numbers != 1 || paras[0].type != NumericVal) {
      print_info (ERROR_INFO, "Routine 'sin' needs 1 numeric parameters.");
      return -1;
   }

   result->type = NumericVal;
   result->val = sin(paras[0].val*PI/180);
   return 0;
}

static int logo_cos(value_t* paras, int para_numbers, value_t* result)
{
   assert (result);

   if (para_numbers != 1 || paras[0].type != NumericVal) {
      print_info (ERROR_INFO, "Routine 'cos' needs 1 numeric parameters.");
      return -1;
   }

   result->type = NumericVal;
   result->val = cos(paras[0].val*PI/180);
   return 0;
}

static int logo_inputwindow (value_t* paras, int para_number, value_t* result)
{
   char buf[INPUT_SIZE];
   char* prompt;

   if (para_number != 1 || paras[0].type == NoneVal) {
      print_info (ERROR_INFO, "Routine 'inputwindow' needs 1 numeric or string parameters.");
      return -1;
   }
   assert (g_exec);
   if (paras[0].type == NumericVal) {
      snprintf (buf, INPUT_SIZE, "%g", paras[0].val);
      prompt = buf; 
   } else {
      prompt = paras[0].str;
   }
   strncpy (buf, g_exec->inputwindow(prompt), INPUT_SIZE);

   if (is_numeric (buf)) {
      double v = 0;
      sscanf (buf, "%lf", &v);
      result->type = NumericVal;
      result->val = v;
      return 0;
   }

   result->type = StringVal;
   result->str = (char*) malloc (strlen (buf) +1);
   CHECK_MEM (result->str);
   strcpy (result->str, buf);
   return 0;
}

#define IS_DIGIT(x) (x >= '0' && x <='9')

static int is_numeric (char* s)
{
   char* cur = s;
   int len;
   int nbr_of_point = 0;

   len = strlen(s);

   if (len == 0) {
      return 0;
   }

   if (s[0] == '-' && len == 1) {
      return 0;
   }

   /* the 1st char*/
   if (!IS_DIGIT(*cur) && *cur != '-') {
      return 0;
   } 
   cur++;

   while (*cur) {
      if (IS_DIGIT(*cur)) {
         cur++;
         continue;
      }
      if (*cur != '.') {
         return 0;
      }
      if (nbr_of_point != 0) {
         /* two point in this string.*/
         return 0;
      }
      nbr_of_point = 1;
      cur++;
   }
   return 1;
}

exe_handle_t create_exe_handle()
{
   rte_t* rte = new rte_t;
   if (!rte) {
      return NULL;
   }

   rte->mutex = CreateMutex(NULL, FALSE, NULL);
   return (exe_handle_t)rte;
}

void clear_for_rerun(exe_handle_t h)
{
   assert (h);
   rte_t* rte = (rte_t*)h;

   GET_MUTEX (rte->mutex);
   if (rte->rtn_lst) {
      delete_routine_list (rte->rtn_lst);
   }
   rte->rtn_lst = NULL;
   for (int i = 0; i <= rte->stack.top; i++) {
      if (rte->stack.var[i]) {
         delete_variable_list (rte->stack.var[i]);
      }
   }
   rte->stack.top = -1;
   memset (rte->stack.var, 0, sizeof(rte->stack.var));   
   REL_MUTEX (rte->mutex);
}

void delete_exe_handle(exe_handle_t h)
{
   assert (h);
   rte_t* rte = (rte_t*)h;
   CloseHandle(rte->mutex);
   delete (rte);
}
